/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.ListBox.PositioningFunction;
using System.Collections.Generic;
using System.Diagnostics;
using Xceed.Silverlight.Utils;

namespace Xceed.Silverlight.ListBox
{
  public partial class PanelBase
  {
    internal class PanelLayoutManager
    {
      #region Constructor

      public PanelLayoutManager( PanelBase parentPanel )
      {
        m_panel = parentPanel;
      }

      #endregion

      #region ParentListBox Property

      public ListBox ParentListBox
      {
        get
        {
          return m_panel.ParentListBox;
        }
      }

      #endregion


      #region ContainerDescriptions Property

      internal ContainerDescriptionList ContainerDescriptions
      {
        get
        {
          return m_containerDescriptions;
        }
      }

      #endregion

      #region DataPathToContainerMapping

      internal Dictionary<DataPath, ListBoxContainer> DataPathToContainerMapping
      {
        get
        {
          return m_dataPathToContainerMapping;
        }
      }

      #endregion

      #region StickyHeader Property

      internal ListBoxContainer StickyHeader
      {
        get
        {
          return m_stickyHeader;
        }
      }

      #endregion

      #region IsLayoutInhibited Internal Property

      internal bool IsLayoutInhibited
      {
        get
        {
          return ( m_inhibitLayoutCount > 0 );
        }
      }

      #endregion


      #region UpdateViewportContent Methods

      private void RecycleContainersBeforeUpdatingViewportContent()
      {
        Dictionary<DataPath, ListBoxContainer> previousDataPathToContainerMapping =
            new Dictionary<DataPath, ListBoxContainer>( m_dataPathToContainerMapping );

        foreach( KeyValuePair<DataPath, ListBoxContainer> keyValuePair in previousDataPathToContainerMapping )
        {
          DataPath dataPath = keyValuePair.Key;
          ListBoxContainer container = keyValuePair.Value;

          if( m_containerDescriptions[ dataPath ] == null )
          {
            this.RemoveDataPathToContainerMapping( dataPath );

            if( !m_isUpdatingViewportForTransition )
            {
              // Even though we do not explicitly want to defer container recycling
              // the container will be put in the waiting list if it is not elligible
              // for recycling.
              m_panel.RecycleContainerAndLayoutOutOfView( container );
            }
            else
            {
              // Transition under way.
              // Add container to the recycling waiting list for future recycling.
              m_panel.AddContainerToRecyclingWaitingList( container );

              if( !FocusHelper.HasFocus( container ) )
              {
                container.UpdateLayoutTransform();
              }
            }
          }
        }
      }

      private ContainerDescriptionList PrepareViewportContent( Size viewport )
      {
        ListBox listBox = this.ParentListBox;
        int numberOfMoveInDataSource = 0;
        ContainerDescriptionList containerDescriptions = new ContainerDescriptionList();
        Rect lastFirstPassContainer = Rect.Empty;
        int totalNumberOfItemsLayouted = 0;

        // Data management
        DataSourceWrapper dataSourceWrapper = listBox.DataSourceWrapper;
        DataSourceEnumeratorWrapper dataSourceEnumerator = listBox.DataSourceEnumeratorWrapper;

        if( dataSourceEnumerator.IsCachingEnumerator )
        {
          //* 2 is a safe value to ensure cache will keep elements in current layout.
          dataSourceEnumerator.SetCacheSize( Int32.MaxValue );
        }

        if( dataSourceEnumerator == null )
          return null;

        // Do not use the Enumerator if there are no items
        // in the DataSource.
        if( dataSourceEnumerator.ItemCount == 0 )
          return containerDescriptions;

        // TopBottom Layout Pass
        numberOfMoveInDataSource = this.LayoutFromCurrentToViewportEnd( viewport, containerDescriptions );
        totalNumberOfItemsLayouted += numberOfMoveInDataSource;

        // Roll back the DataEnumerator
        if( numberOfMoveInDataSource > 0 )
          m_panel.EnumeratorHelper.Move( -( numberOfMoveInDataSource ) );

        // BottomTop Layout Pass
        numberOfMoveInDataSource = this.LayoutFromCurrentToViewportStart( viewport, containerDescriptions );

        totalNumberOfItemsLayouted += numberOfMoveInDataSource;


        // Start roll backing the DataEnumerator.
        int offsetToGetOnFirstLayoutedItem = numberOfMoveInDataSource;
        if( offsetToGetOnFirstLayoutedItem > 0 )
        {
          int offsetToGetBeforeFirstLayoutedItem = offsetToGetOnFirstLayoutedItem - 1;

          if( m_panel.EnumeratorHelper.Move( offsetToGetBeforeFirstLayoutedItem ) )
          {
            offsetToGetOnFirstLayoutedItem -= offsetToGetBeforeFirstLayoutedItem;
          }
          else
          {
            m_panel.EnumeratorHelper.Move( 1 );
            offsetToGetOnFirstLayoutedItem = 0;
          }
        }

        if( dataSourceEnumerator.IsCachingEnumerator )
        {
          //* 4 is a safe value to ensure cache will keep elements in current layout.
          dataSourceEnumerator.SetCacheSize( totalNumberOfItemsLayouted * 4 + 1 );

        }

        // Complete the roll back of the DataEnumerator.
        if( offsetToGetOnFirstLayoutedItem > 0 )
        {
          m_panel.EnumeratorHelper.Move( offsetToGetOnFirstLayoutedItem );
        }

        // Correct the viewport height according to the positioning function.
        m_panel.PositioningFunction.CorrectViewportSize( m_panel.m_itemsInViewport, ref m_panel.m_viewport );

        ListBoxContainerConfigurationBase groupConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );

        if( ( m_panel.AllowStickyHeaders ) &&
            ( groupConfiguration.IsSticky ) )
        {
          m_panel.PrepareStickyHeaderDescription( containerDescriptions );
        }

        this.UpdateScrollToTopBottomIndexes( containerDescriptions );

        return containerDescriptions;
      }

      private void UpdateScrollToTopBottomIndexes( ContainerDescriptionList containerDescriptions )
      {
        // Find out how many items are completely out of view and the Item count (without GroupHeaders)
        int outOfViewAtTopItemCount = 0;
        int outOfViewAtTopNonGroupHeaderItemCount = 0;
        int outOfViewAtBottomItemCount = 0;
        int outOfViewAtBottomNonGroupHeaderItemCount = 0;
        int totalItemCount = 0;
        bool foundFirstVisibleItem = false;

        foreach( ContainerDescription containerDescription in containerDescriptions )
        {
          bool isNotGroupHeader = !( containerDescription.Configuration is ListBoxGroupHeaderConfiguration );

          if( containerDescription.ProportionInEffectiveViewport <= 0.0d )
          {
            if( !foundFirstVisibleItem )
            {
              outOfViewAtTopItemCount++;

              if( isNotGroupHeader )
              {
                outOfViewAtTopNonGroupHeaderItemCount++;
              }
            }
            else
            {
              outOfViewAtBottomItemCount++;

              if( isNotGroupHeader )
              {
                outOfViewAtBottomNonGroupHeaderItemCount++;
              }
            }
          }
          else
          {
            foundFirstVisibleItem = true;
          }

          if( isNotGroupHeader )
          {
            totalItemCount++;
          }
        }

        // Find the top and bottom item visible proportions, as all in between will be 100% visible
        double topItemVisibleProportion = ( outOfViewAtTopItemCount < containerDescriptions.Count ) ? containerDescriptions[ outOfViewAtTopItemCount ].ProportionInEffectiveViewport : 0;
        double topItemAdjustedVisibleProportion = topItemVisibleProportion;
        // if description is a group
        if( ( outOfViewAtTopItemCount < containerDescriptions.Count ) && ( containerDescriptions[ outOfViewAtTopItemCount ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          topItemAdjustedVisibleProportion = ( 1 - m_panel.GetGroupHeaderProportion() + m_panel.GetGroupHeaderProportion() * topItemVisibleProportion );
        }
        // if previous description is a group
        else if( ( outOfViewAtTopItemCount > 0 ) && ( outOfViewAtTopItemCount <= containerDescriptions.Count ) &&
          ( containerDescriptions[ outOfViewAtTopItemCount - 1 ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          topItemAdjustedVisibleProportion = topItemVisibleProportion * ( 1 - m_panel.GetGroupHeaderProportion() );
        }

        double bottomItemVisibleProportion = containerDescriptions[ containerDescriptions.Count - 1 - outOfViewAtBottomItemCount ].ProportionInEffectiveViewport;
        double bottomItemAdjustedVisibleProportion = bottomItemVisibleProportion;
        // if description is a group
        if( ( outOfViewAtBottomItemCount > 0 ) && ( ( containerDescriptions.Count - 1 ) >= outOfViewAtBottomItemCount ) && 
          ( containerDescriptions[ containerDescriptions.Count - 1 - outOfViewAtBottomItemCount ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          bottomItemAdjustedVisibleProportion = bottomItemVisibleProportion * m_panel.GetGroupHeaderProportion();
        }
        // if previous description is a group
        else if( ( totalItemCount > 1 ) && ( ( containerDescriptions.Count - 2 ) >= outOfViewAtBottomItemCount ) &&
          ( containerDescriptions[ containerDescriptions.Count - 2 - outOfViewAtBottomItemCount ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          bottomItemAdjustedVisibleProportion = bottomItemVisibleProportion * ( 1 - m_panel.GetGroupHeaderProportion() ) + m_panel.GetGroupHeaderProportion();
        }

        // Calculate offset created by the presence of a StickyHeader
        double stickyHeaderOffset = 0.0d;
        if( containerDescriptions.ForcedStickyContainerDescription != null )
        {
          stickyHeaderOffset = m_panel.GetGroupHeaderProportion() / ( 1 - m_panel.GetGroupHeaderProportion() );
        }

        // Determine scroll indexes for all container descriptions
        ContainerDescription currentDescription = null;
        int itemCount = 0;
        bool inhibitStickyHeaderOffsetting = false;
        for( int i = 0; i < containerDescriptions.Count; i++ )
        {
          currentDescription = containerDescriptions[ i ];
          double containerTopItemVisibleProportion = ( i == outOfViewAtTopItemCount ) ? topItemAdjustedVisibleProportion : topItemVisibleProportion;
          double containerBottomItemVisibleProportion = ( i == containerDescriptions.Count - 1 - outOfViewAtBottomItemCount ) ? bottomItemAdjustedVisibleProportion : bottomItemVisibleProportion;

          // Calculate scroll indexes
          currentDescription.ScrollIndexAsTopItem = m_panel.ScrollManager.ActualScrollIndex + ( itemCount - 1 ) + containerTopItemVisibleProportion - outOfViewAtTopNonGroupHeaderItemCount;
          currentDescription.ScrollIndexAsBottomItem = m_panel.ScrollManager.ActualScrollIndex - ( totalItemCount - 1 - itemCount ) + ( 1 - containerBottomItemVisibleProportion ) + outOfViewAtBottomNonGroupHeaderItemCount;

          bool isGroupHeader = currentDescription.Configuration is ListBoxGroupHeaderConfiguration;

          // Offset ScrollIndexAsTopItem unless previous item or current description is a group header
          if( (!inhibitStickyHeaderOffsetting) && (!isGroupHeader) )
          {
            currentDescription.ScrollIndexAsTopItem -= stickyHeaderOffset;
          }

          // Inhibit StickyHeader offseting for next item if this a Group Header
          inhibitStickyHeaderOffsetting = isGroupHeader;

          // Increase the itemCount if it's not a GroupHeader
          if( !isGroupHeader )
          {
            itemCount++;
          }
        }
      }

      internal void UpdateViewportContent( Size viewport )
      {
        if( !m_panel.IsInitialized )
        {
          m_panel.Initialize( viewport );
          return;
        }

        if( this.IsLayoutInhibited )
          return;

        ListBox listBox = this.ParentListBox as ListBox;
        bool needRecursiveCall = false;
        bool isScrollManagerActive = m_panel.ScrollManager.IsScrollManagerActive;
        ListBoxContainer currentFocusContainer = m_panel.SelectionAndFocusManager.FindFocusedContainer();

        IScalingFunction scalingFunction = m_panel.GetScalingFunction();

        using( this.DeferUpdateViewport() )
        {
          m_containerDescriptions = this.PrepareViewportContent( viewport );

          if( m_containerDescriptions == null )
            return;

          this.RecycleContainersBeforeUpdatingViewportContent();

          int containerDescriptionCount = m_containerDescriptions.Count;
          int containerCounter = 0;
          for( int i = 0; i < containerDescriptionCount; i++ )
          {
            ContainerDescription containerDescription = m_containerDescriptions[ i ];
            DataPath containerDataPath = containerDescription.DataPath;
            ListBoxContainerConfigurationBase configuration = containerDescription.Configuration;
            ListBoxContainer container = null;

            m_dataPathToContainerMapping.TryGetValue( containerDataPath, out container );

            // Is the focus container contains the dataPath we want to display ?
            if( ( container == null ) &&
              ( currentFocusContainer != null ) )
            {
              // If the current focused container as been recycled
              if( currentFocusContainer.DataPath != null )
              {
                // The current focus container is coming back in view
                if( currentFocusContainer.DataPath.Equals( containerDataPath ) )
                {
                  // The container is the focused container
                  container = currentFocusContainer;

                  // Do not recycle this container
                  m_panel.m_recyclingWaitingList.Remove( container );

                  if( container != m_stickyHeader )
                  {
                    // Add this container to the DataPath to Container mapping
                    this.AddDataPathToContainerMapping( containerDataPath, container );
                  }
                }
              }
            }

            // The container was not the focused container and not found in the current DataPath to 
            // container mapping
            if( container == null )
            {
              container = m_panel.GetAvailableContainer( configuration, i );

              listBox.PrepareContainerForItem( container, containerDataPath );
              container.Measure( viewport );

              // Update the configuration size
              if( configuration.UpdateCachedSize( container ) )
              {
                // When the cached size is updated, 
                // we need to recursively call UpdateViewportContent 
                // to ensure the size and position of each element
                // is correctly represented in the ContainerDescriptions
                needRecursiveCall = true;
              }

              this.AddDataPathToContainerMapping( containerDataPath, container );
            }
            else if( container.IsDirty )
            {
              listBox.PrepareContainerForItem( container, containerDataPath );
              container.Measure( viewport );

              // Update the configuration size
              if( configuration.UpdateCachedSize( container ) )
              {
                // When the cached size is updated, 
                // we need to recursively call UpdateViewportContent 
                // to ensure the size and position of each element
                // is correctly represented in the ContainerDescriptions
                needRecursiveCall = true;
              }
            }

            // To avoid recycling a container with the IsMouseOver state
            // while scrolling
            if( isScrollManagerActive )
              container.IsMouseOver = false;

            container.AbsoluteOpacity = containerDescription.OpacityFactor;
            container.AbsoluteScaleFactor = containerDescription.ScalingFactor;
            container.AbsoluteRotationAngle = containerDescription.RotateAngle;

            // The maximum height for a container is the viewport height
            container.MaximumHeight = viewport.Height;
            // The maximum width for a container is the viewport width
            container.MaximumWidth = viewport.Width;

            container.AbsoluteOffset = containerDescription.TargetPosition;

            if( containerDescription.ZIndex != ZIndexFunction.NoZIndex )
            {
              container.ZOrder = containerDescription.ZIndex;
            }
            else
            {
              // containerDescription.ScalingFactor is computed using the ScalingFunction
              if( scalingFunction != null )
              {
                container.ZOrder = ( int )( containerDescriptionCount * containerDescription.ScalingFactor );
              }
              else if( ( containerDataPath != null ) && ( containerDataPath.LastChild is GroupDataItem ) )
              {
                // Ensure the ZOrder is greater than the number of containers in the viewport
                // when a Group Break is detected
                container.ZOrder = m_containerDescriptions.Count * 2;
              }
              else
              {
                container.ZOrder = containerCounter;
              }
            }

            containerCounter++;

            m_panel.AdjustContainerTargetPosition( container );

            if( !m_isUpdatingViewportForTransition )
            {
              container.UpdateLayoutTransform();
              container.UpdateSize();
            }

            if( needRecursiveCall )
              break;
          }

          this.UpdateStickyHeader( viewport );

          ListBoxContainer focusedContainer = m_panel.SelectionAndFocusManager.FindFocusedContainer();

          // Layout the FocusedContainer out of view if it was not layouted
          if( ( focusedContainer != null ) && ( focusedContainer != m_stickyHeader ) )
          {
            DataPath focusedContainerDataPath = focusedContainer.DataPath;

            // If the focused container DataPath isn't is the container descriptions means the container isn't in
            // the layout. So we need to layout it out of view. We can't just recycle it, we need to have
            // a reference on it so we can bring it into view.
            if( ( focusedContainerDataPath != null ) && ( m_containerDescriptions[ focusedContainerDataPath ] == null ) )
            {
              // Only layout the focused container out of view if no animations
              // are currently running
              if( !m_isUpdatingViewportForTransition && ( m_transitionAnimations.Count == 0 ) )
              {
                focusedContainer.AbsoluteOffset = PanelBase.OutOfViewPoint;
                focusedContainer.UpdateLayoutTransform();
                focusedContainer.UpdateSize();
              }
            }
            // Do not add it to DataPathToContainerMapping since it is 
            // currently layouted out of view and it will be fetched
            // using this.Children when UpdateCachedFocusedContainer
            // will be called
          }

          // Only call UpdateViewportCompleted when
          // no recursive call is required
          if( !needRecursiveCall )
            this.OnUpdateViewportCompleted();
        }

        // This must be called OUTSIDE the DeferViewportUpdate
        // else it will have no effect
        if( needRecursiveCall )
        {
          //TransitionAnimation configurationChangeTransition = new FadingItemsSizeChangedTransitionAnimation();
          //if( !this.LaunchTransition( configurationChangeTransition, this.ViewportSizeInPixels ) )
          this.UpdateViewportContent( viewport );
        }

      }

      private void UpdateStickyHeader( Size viewport )
      {
        // Recycle the sticky header if we don't need it anymore or if
        // the group hearder changed
        bool applyPrepare = false;
        bool recycleStickyHeader = false;

        if( m_stickyHeader != null )
        {
          if( m_containerDescriptions.ForcedStickyContainerDescription == null )
          {
            recycleStickyHeader = true;
          }
          else if( !m_containerDescriptions.ForcedStickyContainerDescription.DataPath.Equals( m_stickyHeader.DataPath ) )
          {
            recycleStickyHeader = true;
          }

          if( recycleStickyHeader )
          {
            m_stickyHeader.RecycleEvenIfFocused = true;
            m_panel.AddContainerToRecyclingWaitingList( m_stickyHeader );
            m_stickyHeader = null;
          }
        }

        // If we need to display the sticky header
        if( m_containerDescriptions.ForcedStickyContainerDescription != null )
        {
          ContainerDescription stickyDescription = m_containerDescriptions.ForcedStickyContainerDescription;
          ListBoxContainerConfigurationBase stickyConfiguration = stickyDescription.Configuration;

          // Try to dequeue one container for this configuration.
          // DO NOT verify if it is already in m_dataPathToContainerMapping
          // since it is not part of the normal layout. It is forced
          // into the layout as the first item of the Viewport
          if( m_stickyHeader == null )
          {
            m_stickyHeader = m_panel.GetAvailableContainer( stickyConfiguration, 999 );
            applyPrepare = true;
          }
          else if( m_stickyHeader.Configuration != stickyConfiguration )
          {
            // Make sure to prepare the container if the 
            // configuration changed
            applyPrepare = true;
          }

          if( applyPrepare )
          {
            this.ParentListBox.PrepareContainerForItem( m_stickyHeader, stickyDescription.DataPath );
            m_stickyHeader.Measure( viewport );

            // Update the configuration size
            stickyConfiguration.UpdateCachedSize( m_stickyHeader );
          }

          // The maximum height for a container is the viewport height
          m_stickyHeader.MaximumHeight = viewport.Height;
          // The maximum width for a container is the viewport width
          m_stickyHeader.MaximumWidth = viewport.Width;

          m_stickyHeader.AbsoluteOpacity = 1.0;
          m_stickyHeader.AbsoluteOffset = stickyDescription.TargetPosition;
          m_stickyHeader.AbsoluteRotationAngle = stickyDescription.RotateAngle;
          m_stickyHeader.ZOrder = ZIndexFunction.MaxZIndex;

          if( !m_isUpdatingViewportForTransition )
          {
            m_stickyHeader.UpdateLayoutTransform();
            m_stickyHeader.UpdateSize();
          }
        }
      }

      private void OnUpdateViewportCompleted()
      {
        if( !m_isUpdatingViewportForTransition )
        {
          m_panel.ProcessRecyclingWaitingList();
        }

        m_panel.ScrollManager.AverageItemSize = m_panel.GetAverageContainerSize();

      }

      #endregion

      #region DataPath to Container mapping

      private void ClearDataPathToContainerMapping()
      {
        m_dataPathToContainerMapping.Clear();
      }

      internal bool RemoveDataPathToContainerMapping( DataPath dataPath )
      {
        return m_dataPathToContainerMapping.Remove( dataPath );
      }

      internal void AddDataPathToContainerMapping( DataPath dataPath, ListBoxContainer container )
      {
        Debug.Assert( m_stickyHeader != container, "We would be adding the sticky header to the dataPathToContainerMapping." );
        m_dataPathToContainerMapping.Add( dataPath, container );
      }

      #endregion

      #region Layout Containers Methods

      private int LayoutFromCurrentToViewportEnd( Size viewport, ContainerDescriptionList containerDescriptions )
      {
        ListBox listBox = this.ParentListBox;
        DataPath currentDataPath = null;

        int numberOfLayoutedItems = 0;
        bool isEnumeratorAfterLast = false;
        double partialOffset = ( m_panel.ItemOffset % 1 );

        // Group
        bool isGroupBreak = false;

        PositioningFunctionBase positioningFunction = m_panel.PositioningFunction;

        // Keep a reference on each configuration to minimize the use of reflexion
        ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
        ListBoxContainerConfigurationBase groupConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );
        m_panel.ActualContainerSize = itemConfiguration.PrepareSize;

        Size itemCorrectedPreparedSize = m_panel.PositioningFunction.GetCorrectedContainerSize( itemConfiguration.PrepareSize, viewport );

        Size groupCorrectedPreparedSize = m_panel.PositioningFunction.GetCorrectedContainerSize( groupConfiguration.PrepareSize, viewport );

        positioningFunction.UpdateEffectiveViewportSizeInPixels( m_panel.ViewportSizeInPixels );

        // This methods Resets the positioning function internal states to prepare the next layout pass
        positioningFunction.PrepareLayoutPass( partialOffset, itemCorrectedPreparedSize, groupCorrectedPreparedSize );

        double itemContainersInViewport = 0.0d;
        double containerProportionInViewport = 0.0d;
        DataPath groupDataPath = null;

        bool isNavigatingToGroupWithAsyncData = this.GetIsNavigatingToGroupWithAsyncData();

        while( ( !positioningFunction.IsLayoutFilled( true ) ) && ( !isEnumeratorAfterLast ) )
        {
          // Look for group break
          isGroupBreak = m_panel.IsCurrentDataPathAGroupBreak();
          currentDataPath = m_panel.EnumeratorHelper.CurrentDataPath;
          isGroupBreak |= ( isNavigatingToGroupWithAsyncData && ( numberOfLayoutedItems == 0 ) && ( currentDataPath.Depth > 1 ) );

          // If we hit a group break, we generate au group header and layout it before 
          // the current data path
          if( ( isGroupBreak ) )
          {
            // Get the group DataPath from the current DataPath
            groupDataPath = currentDataPath.CreateAncestorPath( Math.Max( 1, currentDataPath.Depth - 1 ) );

            this.LayoutContainer( true, groupDataPath, containerDescriptions );
          }

          containerProportionInViewport = this.LayoutContainer( true, currentDataPath, containerDescriptions );

          itemContainersInViewport += containerProportionInViewport;

          numberOfLayoutedItems++;

          // Move the enumrator forward
          if( !m_panel.EnumeratorHelper.Move( 1 ) )
            isEnumeratorAfterLast = true;

        }

        m_panel.m_itemsInViewport = itemContainersInViewport;
        return numberOfLayoutedItems;
      }

      private int LayoutFromCurrentToViewportStart( Size viewport, ContainerDescriptionList containerDescriptions )
      {
        ListBox listBox = this.ParentListBox;
        DataPath currentDataPath = null;

        int numberOfMoveBackward = 0;
        bool isEnumeratorBeforeFirst = false;
        double partialOffset = ( m_panel.ItemOffset % 1 );

        // Group
        bool isGroupBreak = false;

        PositioningFunctionBase positioningFunction = m_panel.PositioningFunction;

        // Keep a reference on each configuration to minimize the use of reflexion
        ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
        m_panel.ActualContainerSize = itemConfiguration.PrepareSize;

        double itemContainersInViewport = 0.0d;
        double containerProportionInViewport = 0.0d;
        DataPath groupDataPath = null;

        // Move back from 1 item. The Current item has been layouted be 
        // the TopBottom layout pass.
        numberOfMoveBackward++;
        if( !m_panel.EnumeratorHelper.Move( -1 ) )
          isEnumeratorBeforeFirst = true;

        while( ( !positioningFunction.IsLayoutFilled( false ) ) && ( !isEnumeratorBeforeFirst ) )
        {

          // Look for group break
          isGroupBreak = m_panel.IsCurrentDataPathAGroupBreak();
          currentDataPath = m_panel.EnumeratorHelper.CurrentDataPath;

          // While layout object in reverse (from bottom to top), if we hit a group break
          // we must first layout the item and the layout the group header
          containerProportionInViewport = this.LayoutContainer( false, currentDataPath, containerDescriptions );

          // Count items in viewport (to update the viewport height)
          itemContainersInViewport += containerProportionInViewport;

          // Now, layout the group header
          if( ( isGroupBreak ) )
          {
            // Get the group DataPath from the current DataPath
            groupDataPath = currentDataPath.CreateAncestorPath( Math.Max( 1, currentDataPath.Depth - 1 ) );

            this.LayoutContainer( false, groupDataPath, containerDescriptions );
          }

          numberOfMoveBackward++;
          // Move the enumrator backward
          if( !m_panel.EnumeratorHelper.Move( -1 ) )
            isEnumeratorBeforeFirst = true;

        }

        // Add the number of layouted items in the layout pass to 
        // the actual computed viewport height.
        m_panel.m_itemsInViewport += itemContainersInViewport;
        return numberOfMoveBackward;

      }

      // This methods place the container on the positioning function and
      // add the newly added container description to the container description list
      private double LayoutContainer( bool layoutForward,
        DataPath dataPathToLayout,
        ContainerDescriptionList containerDescriptions )
      {

        ContainerDescription containerDescription = new ContainerDescription();
        PositioningFunctionBase positioningFunction = m_panel.PositioningFunction;
        IOpacityFunction opacityFunction = m_panel.GetOpacityFunction();
        IScalingFunction scalingFunction = m_panel.GetScalingFunction();
        IZIndexFunction zindexFunction = m_panel.GetZIndexFunction();

        // Keep a reference on each configuration to minimize the use of reflexion
        ListBoxContainerConfigurationBase itemConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.Item );
        ListBoxContainerConfigurationBase groupHeaderConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.GroupHeader );

        Size viewport = m_panel.ViewportSizeInPixels;

        Point containerPosition = new Point();
        double containerProportionInViewport = 0.0d;
        int layoutProgressionPercentage = 0;
        double rotateAngle = 0.0d;

        // Get the container configuration from the current DataPath
        containerDescription.Configuration = m_panel.GetContainerConfigurationFromDataPath( dataPathToLayout,
                                                                                         itemConfiguration,
                                                                                         groupHeaderConfiguration );

        // Compute a rough estimated size for the container so the positioning can be more accurate
        m_panel.ComputeEstimatedSizeForConfig( dataPathToLayout, viewport );

        Size preparedSize = containerDescription.Configuration.PrepareSize;

        // Correct the PreparedSize to ensure its value is at most the 
        // Viewport's size
        Size correctedPreparedSize = m_panel.PositioningFunction.GetCorrectedContainerSize( preparedSize, viewport );

        // Ask the positioning function to place the container accordingly to 
        // the layouted direction
        if( layoutForward )
        {
          positioningFunction.PlaceContainerForward( correctedPreparedSize,
            ( containerDescription.Configuration.GetType() == typeof( ListBoxGroupHeaderConfiguration ) ),
            out containerPosition,
            out containerProportionInViewport,
            out layoutProgressionPercentage,
            out rotateAngle );
        }
        else
        {
          positioningFunction.PlaceContainerBackward( correctedPreparedSize,
            ( containerDescription.Configuration.GetType() == typeof( ListBoxGroupHeaderConfiguration ) ),
            out containerPosition,
            out containerProportionInViewport,
            out layoutProgressionPercentage,
            out rotateAngle );
        }

        // Assigned the computed values to the container description
        containerDescription.DataPath = dataPathToLayout;
        containerDescription.TargetPosition = containerPosition;
        containerDescription.RotateAngle = rotateAngle;
        containerDescription.ProportionInEffectiveViewport = containerProportionInViewport;

        containerDescription.OpacityFactor = ( opacityFunction != null )
          ? opacityFunction.GetOpacityFactor( layoutProgressionPercentage, false, dataPathToLayout.LastChild is GroupDataItem )
          : 1;

        containerDescription.ScalingFactor = ( scalingFunction != null )
          ? scalingFunction.GetScalingFactor( layoutProgressionPercentage, false, dataPathToLayout.LastChild is GroupDataItem )
          : 1;

        containerDescription.ZIndex = ( zindexFunction != null )
          ? zindexFunction.GetZIndex( layoutProgressionPercentage, false, dataPathToLayout.LastChild is GroupDataItem )
          : ZIndexFunction.NoZIndex;

        // Add the container description to the container description list
        if( layoutForward )
        {
          containerDescriptions.Add( containerDescription );
        }
        else
        {
          containerDescriptions.Insert( 0, containerDescription );
        }

        // Return the proportion of the container visible in the effective viewport.
        return containerProportionInViewport;
      }

      private bool GetIsNavigatingToGroupWithAsyncData()
      {
        if( m_panel.ParentListBox.IsAsyncDataSource )
        {
          foreach( TransitionAnimation transition in m_transitionAnimations )
          {
            if( ( transition is ScrollingDownTransitionAnimation ) || ( transition is ScrollingUpTransitionAnimation ) || ( transition is LoadingTransitionAnimation ) )
            {
              return true;
            }
          }
        }

        return false;
      }

      #endregion

      #region Transitions Methods

      internal void LaunchViewChangedTransition()
      {
        TransitionAnimation viewChangedTransition = new ViewChangingTransitionAnimation( this.ParentListBox.AnimationSettings );
        viewChangedTransition.Completed += this.OnTransitionCompleted;

        if( !this.LaunchTransition( viewChangedTransition, m_panel.ViewportSizeInPixels, null ) )
        {
          this.UpdateViewportContent( m_panel.ViewportSizeInPixels );
        }
      }

      internal void LaunchLoadingTransition( Size viewport )
      {
        if( !this.LaunchTransition( new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings ), viewport, null ) )
        {
          this.UpdateViewportContent( viewport );
        }
      }

      internal bool LaunchTransition( TransitionAnimation transition, Size viewport, object state )
      {
        if( transition == null )
          return false;

        transition.Completed += this.OnTransitionCompleted;

        m_transitionAnimations.Add( transition );

        // Initialize the transition with the actual viewport content
        transition.Initialize( this.GetLayoutedContainers( true ), state );

        // Make sure the configuration has been applied
        m_panel.Refresh();

        // Update the viewport and make sure no recycling happens
        m_isUpdatingViewportForTransition = true;

        this.UpdateViewportContent( viewport );

        m_isUpdatingViewportForTransition = false;

        // Start the transition with the resulting viewport content
        transition.StartTransitionAnimation( this.GetLayoutedContainers( true ) );

        return true;
      }

      private void OnTransitionCompleted( object sender, TransitionAnimationEventArgs e )
      {
        TransitionAnimation transition = sender as TransitionAnimation;

        if( transition == null )
          throw new ListBoxInternalException();

        transition.Completed -= this.OnTransitionCompleted;

        m_transitionAnimations.Remove( transition );

        //This will go through all the elements marked for removal in the transitions queue 
        //and "remove" those which do not have any ongoing transition animations.
        m_panel.ProcessRecyclingWaitingList();
      }

      internal void AddNewContainerToCurrentTransitions( ListBoxContainer container )
      {
        foreach( TransitionAnimation transition in m_transitionAnimations )
        {
          transition.AddContainer( container );
        }
      }

      internal bool GetHasRunningTransitionAnimations()
      {
        return m_transitionAnimations.Count > 0;
      }

      #endregion

      #region BringIntoView Methods

      internal bool BringContainerCompletlyIntoView( ListBoxContainer targetContainer, NavigationInfo navigationInfo )
      {
        // If we can't get the container description neither the positioning function
        // we can't find the position of the next container (we don't know anything about the layout).
        if( ( m_containerDescriptions == null ) ||
            ( m_panel.PositioningFunction == null ) ||
            ( !m_panel.PositioningFunction.IsScrollOffsetDifferenceBetweenPositionsSupported ) )
          return true;

        BringContainerCompletelyIntoViewNavigationAction navigationAction = new BringContainerCompletelyIntoViewNavigationAction( targetContainer.DataPath, m_panel, navigationInfo );
        double targetIndex = navigationAction.CalculateTargetScrollIndex();

        if( LayoutUtils.AreClose( targetIndex, m_panel.ScrollManager.TargetIndex ) )
          return false;

        // Scroll to the target.
        m_panel.StartScrollingAnimation( targetIndex, navigationInfo.IsPageScrolling, navigationAction );

        return true;
      }

      internal bool IsContainerOutOfViewport( ListBoxContainer container )
      {
        return this.IsContainerOutOfViewport( container, false );
      }

      internal bool IsContainerOutOfViewport( ListBoxContainer container, bool allowPartiallyVisible )
      {
        if( !m_panel.IsContainerOutOfViewportSupported )
          return false;

        if( container == null )
          return true;

        Rect panelRect = new Rect( new Point(), m_panel.RenderSize );

        // Correct the visibility rectangle so container under the sticky header are not marked as visible.
        // If no sticky header, the visibility rectangle remains unchanged.
        Size stickyHeaderSize = new Size( 0.0d, 0.0d );

        if( m_containerDescriptions != null )
        {
          if( m_containerDescriptions.ForcedStickyContainerDescription != null )
          {
            stickyHeaderSize = new Size( m_stickyHeader.ActualElementWidth, m_stickyHeader.ActualElementHeight );
          }
        }

        panelRect = m_panel.PositioningFunction.GetCorrectedViewportVisibilityRect( stickyHeaderSize, panelRect );

        Rect containerRect = new Rect( new Point(), container.RenderSize );
        Rect targetContainerToPanelRect = container.TransformToVisual( m_panel ).TransformBounds( containerRect );

        panelRect.Intersect( targetContainerToPanelRect );

        // Container is partially visible if intersection is not empty, completely visible if intersection
        // is less than a pixel away from the container size, due to pixel size approximation errors.
        bool isPartiallyVisible = ( panelRect.Height > 0d ) && ( panelRect.Width > 0d );
        bool isCompletelyVisible = ( ( containerRect.Height - panelRect.Height ) < 1 ) && ( ( containerRect.Width - panelRect.Width ) < 1 );

        if( allowPartiallyVisible && isPartiallyVisible )
          return false;

        // If partial visibility is not allowed and confirmed, a container that is
        // not completely into view is out of viewport.
        return !isCompletelyVisible;
      }

      internal DataPath GetNextOrPreviousDataPathForCurrentDataPath( DataPath currentPath, NavigationInfo navigationInfo )
      {
        int currentPathIndex = m_containerDescriptions.IndexOf( currentPath );

        if( currentPathIndex == -1 )
          return null;

        ListBoxContainer nextOrPreviousSelectableContainer = null;

        do
        {
          if( navigationInfo.IsForwardScrolling )
          {
            // We need to move in container description to get the next selectable container

            while( ( nextOrPreviousSelectableContainer == null ) && ( currentPathIndex < m_containerDescriptions.Count - 1 ) )
            {
              ContainerDescription nextContainerDescription = m_containerDescriptions[ currentPathIndex + 1 ];

              nextOrPreviousSelectableContainer = this.GetContainerFromPath( nextContainerDescription.DataPath, true );
              currentPathIndex++;
            }
          }
          else
          {
            while( ( nextOrPreviousSelectableContainer == null ) && ( currentPathIndex >= 1 ) )
            {
              ContainerDescription previousContainerDescription = m_containerDescriptions[ currentPathIndex - 1 ];

              nextOrPreviousSelectableContainer = this.GetContainerFromPath( previousContainerDescription.DataPath, true );
              currentPathIndex--;
            }
          }

          // Ensure to get the next container with a different DataPath
        } while( ( nextOrPreviousSelectableContainer != null )
                 && ( nextOrPreviousSelectableContainer.DataPath != null )
                 && ( nextOrPreviousSelectableContainer.DataPath.Equals( currentPath ) ) );

        return ( nextOrPreviousSelectableContainer != null )
              ? nextOrPreviousSelectableContainer.DataPath
              : null;
      }

      // Must find the first or last fully visible and return it, else null to force a scroll to an index +/- viewport away
      internal DataPath GetFirstOrLastFullyVisibleDataPathForCurrentDataPath(
        DataPath currentPath,
        NavigationInfo navigationInfo )
      {
        double scrollManagerTargetIndex = m_panel.ScrollManager.TargetIndex;
        int containerDescriptionCount = m_containerDescriptions.Count;
        double viewport = m_panel.ItemsInViewport;
        double maxTargetIndex = this.ParentListBox.DataSourceEnumeratorWrapper.ItemCount - viewport;

        //If we are already on the last full page and doing page scrolling
        if( navigationInfo.IsForwardScrolling && ( scrollManagerTargetIndex >= maxTargetIndex ) )
        {
          //return the last element.
          if( containerDescriptionCount > 0 )
          {
            return m_containerDescriptions[ containerDescriptionCount - 1 ].DataPath;
          }
          else
          {
            return null;
          }
        }
        //If we are already on the first full page and doing page scrolling
        else if( !navigationInfo.IsForwardScrolling && ( scrollManagerTargetIndex == 0 ) )
        {
          //return the first element.
          if( containerDescriptionCount > 0 )
          {
            return m_containerDescriptions[ 0 ].DataPath;
          }
          else
          {
            return null;
          }
        }
        else
        {
          if( navigationInfo.IsForwardScrolling )
          {
            // We want the last fully visible DataPath in the viewport or null
            // if the currentDataPath is already the last fully visible
            DataPath lastFullyVisibleDataPath = this.GetLastFullyVisibleDataPath();

            if( ( lastFullyVisibleDataPath != null ) && ( !lastFullyVisibleDataPath.Equals( currentPath ) ) )
            {
              return lastFullyVisibleDataPath;
            }
            else
            {
              return null;
            }
          }
          else
          {
            // We want the first fully visible DataPath in the viewport or null
            // if the currentDataPath is already the first fully visible
            DataPath firstFullyVisibleDataPath = this.GetFirstFullyVisibleDataPath();

            if( ( firstFullyVisibleDataPath != null ) && ( !firstFullyVisibleDataPath.Equals( currentPath ) ) )
            {
              return firstFullyVisibleDataPath;
            }
            else
            {
              return null;
            }
          }
        }
      }

      #endregion

      #region Internal Methods

      internal IDisposable DeferUpdateViewport()
      {
        return new DeferLayoutUpdateDisposable( this );
      }

      internal void Reset()
      {
        if( m_stickyHeader != null )
        {
          // Even though we do not explicitly want to defer container recycling
          // the container will be put in the waiting list if it is not elligible
          // for recycling.
          m_stickyHeader.RecycleEvenIfFocused = true;
          m_panel.RecycleContainerAndLayoutOutOfView( m_stickyHeader );
          m_stickyHeader = null;
        }

        this.ClearDataPathToContainerMapping();
      }

      internal DataPath GetFirstFullyVisibleDataPath()
      {
        int count = m_containerDescriptions.Count;

        if( count == 0 )
          return null;

        int index = 0;

        while( index < count )
        {
          DataPath dataPath = m_containerDescriptions[ index ].DataPath;

          ListBoxContainer container = this.GetContainerFromPath( dataPath, true );

          // null container will be received for group headers
          if( container != null )
          {
            if( !this.IsContainerOutOfViewport( container ) )
            {
              return dataPath;
            }
          }

          index++;
        }

        return null;
      }

      internal DataPath GetLastFullyVisibleDataPath()
      {
        int count = m_containerDescriptions.Count;

        if( count == 0 )
          return null;

        while( count > 0 )
        {
          DataPath dataPath = m_containerDescriptions[ count - 1 ].DataPath;

          ListBoxContainer container = this.GetContainerFromPath( dataPath, true );

          if( !this.IsContainerOutOfViewport( container ) )
            return dataPath;

          count--;
        }

        return null;
      }

      internal IEnumerable<ListBoxContainer> GetLayoutedContainers( bool includeStickyHeader )
      {
        List<ListBoxContainer> layoutedContainers = new List<ListBoxContainer>();
        foreach( ListBoxContainer container in m_dataPathToContainerMapping.Values )
        {
          layoutedContainers.Add( container );
        }

        if( ( m_stickyHeader != null ) && ( includeStickyHeader ) )
        {
          layoutedContainers.Add( m_stickyHeader );
        }

        return layoutedContainers;
      }

      internal Size GetStickyHeaderSize()
      {
        if( ( m_panel.AllowStickyHeaders ) && ( m_containerDescriptions.ForcedStickyContainerDescription != null ) )
        {
          return new Size( m_stickyHeader.AbsoluteElementWidth, m_stickyHeader.AbsoluteElementHeight );
        }

        return new Size( 0.0d, 0.0d );
      }

      internal ListBoxContainer GetContainerFromPath( DataPath dataPath )
      {
        return this.GetContainerFromPath( dataPath, false );
      }

      internal ListBoxContainer GetContainerFromPath( DataPath dataPath, bool selectableOnly )
      {
        if( dataPath == null )
          return null;

        // Use m_dataPathToContainerMapping to return only layouted containers
        foreach( ListBoxContainer container in m_dataPathToContainerMapping.Values )
        {
          if( selectableOnly && !container.IsSelectable )
            continue;

          DataPath containerDataPath = container.DataPath;

          if( containerDataPath == null )
            continue;

          if( containerDataPath.Equals( dataPath ) )
            return container;
        }



        // Check if the DataPath is the focused container's DataPath
        // because the focused container isn't mapped in the dataPathToContainermapping
        ListBoxContainer focusedContainer = m_panel.SelectionAndFocusManager.FindFocusedContainer();

        if( focusedContainer != null )
        {
          if( focusedContainer.DataPath.Equals( dataPath ) )
          {
            return focusedContainer;
          }
        }

        return null;
      }

      #endregion

      #region Private Field

      private PanelBase m_panel;

      private int m_inhibitLayoutCount;
      private bool m_isUpdatingViewportForTransition;

      private List<TransitionAnimation> m_transitionAnimations = new List<TransitionAnimation>();

      private ContainerDescriptionList m_containerDescriptions;
      private Dictionary<DataPath, ListBoxContainer> m_dataPathToContainerMapping = new Dictionary<DataPath, ListBoxContainer>();
      private ListBoxContainer m_stickyHeader;

      #endregion


      #region DeferLayoutUpdateDisposable Private Class

      private class DeferLayoutUpdateDisposable : IDisposable
      {
        public DeferLayoutUpdateDisposable( PanelLayoutManager layoutManager )
        {
          if( layoutManager == null )
            throw new ArgumentNullException( "layoutManager" );

          m_layoutManager = layoutManager;
          m_layoutManager.m_inhibitLayoutCount++;
        }

        public void Dispose()
        {
          if( m_layoutManager == null )
            return;

          m_layoutManager.m_inhibitLayoutCount--;

          if( m_layoutManager.m_inhibitLayoutCount == 0 )
          {
            if( m_layoutManager.m_panel.MeasureOverrideInhibited )
            {
              m_layoutManager.m_panel.InvalidateMeasure();
            }
            else if( m_layoutManager.m_panel.ArrangeOverrideInhibited )
            {
              m_layoutManager.m_panel.InvalidateArrange();
            }

            m_layoutManager.m_panel.MeasureOverrideInhibited = false;
            m_layoutManager.m_panel.ArrangeOverrideInhibited = false;
          }

          m_layoutManager = null;
        }

        private PanelLayoutManager m_layoutManager;
      }

      #endregion
    }
  }
}
